﻿#ifndef QCOMINFO_SYSINFO_HPP
#define QCOMINFO_SYSINFO_HPP

#include "qglobal.h"
#include <QMap>
#include <QList>
#include <QString>
#include <QProcess>
#include <QSysInfo>
#include <QSettings>
#include <QStorageInfo>
#include <QNetworkInterface>
#include "constexprqstring.hpp"

namespace SysInfo
{
    // Constants
    static const qint64           GIGABYTES_IN_BYTES{1000000000};
    static const ConstexprQString CPU_NAME{"wmic cpu get name"};
    static const ConstexprQString GPU_NAME{"wmic PATH Win32_videocontroller get VideoProcessor "};
    static const ConstexprQString VRAM{"wmic PATH Win32_VideoController get AdapterRAM"};
    static const ConstexprQString LINUX_CPU_NAME{"cat /proc/cpuinfo | grep 'model name' | uniq"};
    static const ConstexprQString BIOS_KEY{R"(HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\BIOS)"};

    static const ConstexprQString CPU_NUMBER{"wmic cpu get NumberOfCores"};
    static const ConstexprQString CPU_LOGICAL{"wmic cpu get NumberOfLogicalProcessors"};
    static const ConstexprQString CPU_PROCESSORID{"wmic cpu get processorid"};

    // Get information about system, kernel
    inline QMap<QString, QString> getSystemInformation()
    {
        QMap<QString, QString> systemInfo{};
        systemInfo.insert("CPU Architecture", QSysInfo::currentCpuArchitecture());
        systemInfo.insert("Product type", QSysInfo::prettyProductName());
        systemInfo.insert("Kernel type", QSysInfo::kernelType());
        systemInfo.insert("Kernel version", QSysInfo::kernelVersion());
        systemInfo.insert("Machine Name", QSysInfo::machineHostName());
        return systemInfo;
    }

    // Call system information and format in a string
    inline QString getSystemInformationAsStr()
    {
        QString         infoStrBuilder{""};
        QMap<QString, QString>   t {SysInfo::getSystemInformation()};
        foreach (auto temp , t.keys())
        {
            infoStrBuilder += temp + " : " + t.value(temp) + "\n\n";
        }
        return infoStrBuilder;
    }

    // Get all the mounted volumes number
    inline int volumesNumber()
    {
        int        size = 0;
        foreach (const QStorageInfo storage, QStorageInfo::mountedVolumes())
        {
            if (storage.isReady() && storage.isValid())
            {
                size++;
            }
        }
        return size;
    }

    // Get information about all the mounted volumes
    inline QList<QMap<QString, QString>> getDiskInformation()
    {
        QList<QMap<QString, QString>> diskInfo{};
        foreach (const QStorageInfo storage, QStorageInfo::mountedVolumes())
        {
            if (storage.isValid() && storage.isReady())
            {
                QMap<QString, QString> tempMap{};
                if (!storage.name().isEmpty())
                {
                    tempMap.insert("Name", storage.name());
                }
                tempMap.insert("Root", storage.rootPath());
                tempMap.insert("File system", storage.fileSystemType());
                tempMap.insert("Space available", QString::number(storage.bytesAvailable() / GIGABYTES_IN_BYTES) + " GB");
                tempMap.insert("Total space",  QString::number(storage.bytesTotal() / GIGABYTES_IN_BYTES) + " GB");
                tempMap.insert("Readonly", storage.isReadOnly() ? "Yes" : "No");

                qint64 used =storage.bytesTotal()-storage.bytesAvailable();
                tempMap.insert("Used space",  QString::number(used / GIGABYTES_IN_BYTES) + " GB");
                qreal usage = used * 100.0 / storage.bytesTotal();
                tempMap.insert("Usage",   QString::number(usage, 'f', 2));

                diskInfo.push_back(tempMap);
            }
        }
        return diskInfo;
    }

    // Call volumes information and append in a string
    inline QString getDiskInformationAsStr()
    {
        QString         diskInfoStr(QString::number(volumesNumber()) + " disks\n\n");
        QList<QMap<QString, QString>>      v = SysInfo::getDiskInformation();
        foreach (auto t , v)
        {
            foreach (auto const &key,  t.keys())
            {
                QString v1 = key;
                QString v2 = t.value(key);
                diskInfoStr.append(key + " : " + t.value(key) + "\n");
            }
            diskInfoStr.append("\n\n");
        }
        return diskInfoStr;
    }

    // Get the information about network interfaces
    inline QList<QMap<QString, QString>> getNetworkInformation()
    {
        QList<QMap<QString, QString>> networkInfo{};
        for (const QNetworkInterface  &networkInterface: QNetworkInterface::allInterfaces())
        {
            if (networkInterface.flags().testFlag(QNetworkInterface::IsUp))
            {
                for (const auto &entry: networkInterface.addressEntries())
                {
                    if (entry.ip().toString().contains("."))
                    {
                        QMap<QString, QString> tempMap;
                        tempMap.insert("Interface", networkInterface.name());
                        tempMap.insert("IP", entry.ip().toString());
                        tempMap.insert("MAC", networkInterface.hardwareAddress().toLocal8Bit().constData());
                        networkInfo.push_back(tempMap);
                    }
                }
            }
        }
        return networkInfo;
    }

    // Format network information in a string
    inline QString getNetworkInformationAsStr()
    {
        QString         networkInfoStr{""};
        QList<QMap<QString, QString>>   r{SysInfo::getNetworkInformation()};
        for (const auto &t : r)
        {
            foreach (auto tempValue , t.keys())
            {
                networkInfoStr.append(tempValue + " : " + t.value(tempValue)  + "\n");
            }
            networkInfoStr.append("\n");
        }
        return networkInfoStr;
    }

    // Get cpu and gpu system information
    inline QList<QMap<QString, QString>> getCpuAndGpuInfo()
    {
        QList<QMap<QString, QString>> cpuAndGpuInfo{};
        QProcess                      process_system;
#if defined(Q_OS_LINUX)
        process_system.start(LINUX_CPU_NAME);
        process_system.waitForFinished();
        cpuAndGpuInfo.push_back({{"CPU", process_system.readAllStandardOutput()}});
#elif defined(Q_OS_WIN)
        process_system.start(CPU_NAME);
        process_system.waitForFinished();
        QMap<QString, QString> cpuInfo;
        QString cpuName = process_system.readAllStandardOutput().toUpper();
        cpuName = cpuName.split("\r\n")[1];
        cpuName.remove("\r");
        cpuName.remove("\n");
        cpuInfo.insert("CPU", cpuName);
        cpuAndGpuInfo.push_back(cpuInfo);


        process_system.start(CPU_NUMBER);
        process_system.waitForFinished();
        QMap<QString, QString> cpuNumberMap;
        QString cpuNumber = process_system.readAllStandardOutput().toUpper();
        cpuNumber = cpuNumber.split("\r\n")[1];
        cpuNumber.remove("\r");
        cpuNumber.remove("\n");
        cpuNumberMap.insert("CPU Number", cpuNumber);
        cpuAndGpuInfo.push_back(cpuNumberMap);

        process_system.start(CPU_LOGICAL);
        process_system.waitForFinished();
        QMap<QString, QString> cpuLogicalMap;
        QString cpuLogical = process_system.readAllStandardOutput().toUpper();
        cpuLogical = cpuLogical.split("\r\n")[1];
        cpuLogical.remove("\r");
        cpuLogical.remove("\n");
        cpuLogicalMap.insert("CPU Logical", cpuLogical);
        cpuAndGpuInfo.push_back(cpuLogicalMap);

        process_system.start(CPU_PROCESSORID);
        process_system.waitForFinished();
        QMap<QString, QString> cpuProcessoridMap;
        QString cpuProcessorid = process_system.readAllStandardOutput().toUpper();
        cpuProcessorid = cpuProcessorid.split("\r\n")[1];
        cpuProcessorid.remove("\r");
        cpuProcessorid.remove("\n");
        cpuProcessoridMap.insert("CPU Processorid", cpuProcessorid);
        cpuAndGpuInfo.push_back(cpuProcessoridMap);

        process_system.start(GPU_NAME);
        process_system.waitForFinished();
        QMap<QString, QString> gpuInfo;
        QString gpuName = process_system.readAllStandardOutput().toUpper();
        gpuName = gpuName.split("\r\n")[1];
        gpuName.remove("\r");
        gpuName.remove("\n");
        gpuInfo.insert("GPU", gpuName);
        cpuAndGpuInfo.push_back(gpuInfo);

        process_system.start(VRAM);
        process_system.waitForFinished();
        QMap<QString, QString> ramInfo;
        QString VRAMName = process_system.readAllStandardOutput().toUpper();
        VRAMName = VRAMName.split("\r\n")[1];
        VRAMName.remove("\r");
        VRAMName.remove("\n");
        ramInfo.insert("VRAM", VRAMName);
        cpuAndGpuInfo.push_back(ramInfo);
#endif
        return cpuAndGpuInfo;
    }

    // Format cpu and gpu information in a string
    inline QString getCpuAndGpuInfoAsStr()
    {
        QString         cpuAndGpuInfoStr{""};
       QList<QMap<QString, QString>>       y{SysInfo::getCpuAndGpuInfo()};
        for (auto const &tempInfoPair : y)
        {
            for (auto const &tempValue : tempInfoPair.keys())
            {
                cpuAndGpuInfoStr.append(tempValue + "\n");
                cpuAndGpuInfoStr.append(tempValue  + " : " + tempInfoPair.value(tempValue) + "\n");
            }
            cpuAndGpuInfoStr.append("\n");
        }
        return cpuAndGpuInfoStr;
    }

    // Get bios information
    inline QList<QMap<QString, QString>> getBiosInfo()
    {
        QList<QMap<QString, QString>> biosInfo{};
        QSettings                     settings(BIOS_KEY, QSettings::NativeFormat);
//        biosInfo.push_back(
//                {{"BaseBoard Manufacturer", settings.value("BaseBoardManufacturer", "0").toString()}});
//        biosInfo.push_back({{"BaseBoard Product", settings.value("BaseBoardProduct", "0").toString()}});
//        biosInfo.push_back({{"BIOS Vendor", settings.value("BIOSVendor", "0").toString()}});
//        biosInfo.push_back({{"BIOS Release Date", settings.value("BIOSReleaseDate", "0").toString()}});
//        biosInfo.push_back({{"System Manufacturer", settings.value("SystemManufacturer", "0").toString()}});
//        biosInfo.push_back({{"Product Name", settings.value("SystemProductName", "0").toString()}});

        QMap<QString, QString> biosInfo1; biosInfo1.insert("BaseBoard Manufacturer",settings.value("BaseBoardManufacturer", "0").toString());
        QMap<QString, QString> biosInfo2; biosInfo2.insert("BaseBoard Product",     settings.value("BaseBoardProduct", "0").toString());
        QMap<QString, QString> biosInfo3; biosInfo3.insert("BIOS Vendor",           settings.value("BIOSVendor", "0").toString());
        QMap<QString, QString> biosInfo4; biosInfo4.insert("BIOS Release Date",     settings.value("BIOSReleaseDate", "0").toString());
        QMap<QString, QString> biosInfo5; biosInfo5.insert("System Manufacturer",   settings.value("SystemManufacturer", "0").toString());
        QMap<QString, QString> biosInfo6; biosInfo6.insert("Product Name",          settings.value("SystemProductName", "0").toString());
        biosInfo.push_back(biosInfo1);
        biosInfo.push_back(biosInfo2);
        biosInfo.push_back(biosInfo3);
        biosInfo.push_back(biosInfo4);
        biosInfo.push_back(biosInfo5);
        biosInfo.push_back(biosInfo6);

        return biosInfo;
    }

    // Format bios information in a string
    inline QString getBiosInfoAsStr()
    {
        QString         biosInfoStr{""};
        QList<QMap<QString, QString>>      biosInfo{getBiosInfo()};
        for (auto const &tmpBiosInfoPair : biosInfo)
        {
            for (auto const &tempInfo : tmpBiosInfoPair.keys())
            {
                biosInfoStr.append(tempInfo + " : " +  tmpBiosInfoPair.value(tempInfo));
            }
            biosInfoStr.append("\n");
        }
        return biosInfoStr;
    }
}
#endif //QCOMINFO_SYSINFO_HPP
